package cc.unitmesh.devins.ui.compose.agent.codereview.analysis

import cc.unitmesh.agent.linter.LintFileResult
import cc.unitmesh.agent.linter.LintIssue
import cc.unitmesh.agent.linter.LintResult
import cc.unitmesh.agent.linter.LintSeverity
import cc.unitmesh.agent.linter.Linter
import cc.unitmesh.agent.linter.LinterRegistry
import cc.unitmesh.agent.codereview.ModifiedCodeRange
import cc.unitmesh.agent.diff.DiffHunk
import cc.unitmesh.agent.diff.DiffLine
import cc.unitmesh.agent.diff.DiffLineType
import kotlinx.coroutines.test.runTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

/**
 * Integration test for Code Review Lint filtering logic.
 * Tests the full flow: Linter -> LintExecutor -> LintResultFormatter
 */
class CodeReviewAnalysisIntegrationTest {

    private lateinit var lintExecutor: LintExecutor
    private lateinit var lintResultFormatter: LintResultFormatter
    private lateinit var testLinter: TestLinter

    @BeforeTest
    fun setup() {
        lintExecutor = LintExecutor()
        lintResultFormatter = LintResultFormatter()
        testLinter = TestLinter()

        // Register test linter
        val registry = LinterRegistry.getInstance()
        registry.register(testLinter)
    }

    @Test
    fun testLintExecutor_withModifiedCodeRanges_filtersIssuesCorrectly() = runTest {
        // Given: A file with lint issues
        val filePath = "/project/src/main/Example.kt"
        val projectPath = "/project"

        // Modified code ranges: lines 10-20 and 50-60
        val modifiedCodeRanges = mapOf(
            filePath to listOf(
                ModifiedCodeRange(
                    filePath = filePath,
                    elementName = "functionA",
                    elementType = "FUNCTION",
                    startLine = 10,
                    endLine = 20,
                    modifiedLines = listOf(12, 15, 18)
                ),
                ModifiedCodeRange(
                    filePath = filePath,
                    elementName = "functionB",
                    elementType = "FUNCTION",
                    startLine = 50,
                    endLine = 60,
                    modifiedLines = listOf(52, 55)
                )
            )
        )

        // TestLinter will return issues at various lines
        testLinter.setTestIssues(
            filePath,
            listOf(
                LintIssue(line = 5, severity = LintSeverity.ERROR, message = "Error at line 5 (outside range)"),
                LintIssue(line = 12, severity = LintSeverity.WARNING, message = "Warning at line 12 (in range)"),
                LintIssue(line = 18, severity = LintSeverity.ERROR, message = "Error at line 18 (in range)"),
                LintIssue(line = 25, severity = LintSeverity.INFO, message = "Info at line 25 (outside range)"),
                LintIssue(line = 55, severity = LintSeverity.WARNING, message = "Warning at line 55 (in range)"),
                LintIssue(line = 70, severity = LintSeverity.ERROR, message = "Error at line 70 (outside range)")
            )
        )

        // When: Running lint with modified code ranges
        val results = lintExecutor.runLint(
            filePaths = listOf(filePath),
            projectPath = projectPath,
            modifiedCodeRanges = modifiedCodeRanges
        )

        // Then: Only issues in modified ranges should be included
        assertEquals(1, results.size, "Should have results for 1 file")

        val fileResult = results.first()
        assertEquals(filePath, fileResult.filePath)
        assertEquals(3, fileResult.issues.size, "Should filter to 3 issues (lines 12, 18, 55)")
        assertEquals(1, fileResult.errorCount, "Should have 1 error (line 18)")
        assertEquals(2, fileResult.warningCount, "Should have 2 warnings (lines 12, 55)")
        assertEquals(0, fileResult.infoCount, "Should have 0 info")

        // Verify the specific issues
        val issueLines = fileResult.issues.map { it.line }.sorted()
        assertEquals(listOf(12, 18, 55), issueLines)
    }

    @Test
    fun testLintExecutor_withoutModifiedCodeRanges_includesAllIssues() = runTest {
        // Given: A file with lint issues
        val filePath = "/project/src/main/Example.kt"
        val projectPath = "/project"

        testLinter.setTestIssues(
            filePath,
            listOf(
                LintIssue(line = 5, severity = LintSeverity.ERROR, message = "Error at line 5"),
                LintIssue(line = 10, severity = LintSeverity.WARNING, message = "Warning at line 10"),
                LintIssue(line = 15, severity = LintSeverity.INFO, message = "Info at line 15")
            )
        )

        // When: Running lint without modified code ranges
        val results = lintExecutor.runLint(
            filePaths = listOf(filePath),
            projectPath = projectPath,
            modifiedCodeRanges = emptyMap()
        )

        // Then: All issues should be included
        assertEquals(1, results.size)
        val fileResult = results.first()
        assertEquals(3, fileResult.issues.size, "Should include all 3 issues")
        assertEquals(1, fileResult.errorCount)
        assertEquals(1, fileResult.warningCount)
        assertEquals(1, fileResult.infoCount)
    }

    @Test
    fun testLintExecutor_withEmptyModifiedRangesForFile_returnsNoIssues() = runTest {
        // Given: A file with lint issues but no modified ranges for it
        val filePath = "/project/src/main/Example.kt"
        val otherFilePath = "/project/src/main/Other.kt"
        val projectPath = "/project"

        val modifiedCodeRanges = mapOf(
            otherFilePath to listOf(
                ModifiedCodeRange(
                    filePath = otherFilePath,
                    elementName = "otherFunction",
                    elementType = "FUNCTION",
                    startLine = 10,
                    endLine = 20,
                    modifiedLines = listOf(15)
                )
            )
        )

        testLinter.setTestIssues(
            filePath,
            listOf(
                LintIssue(line = 5, severity = LintSeverity.ERROR, message = "Error at line 5"),
                LintIssue(line = 10, severity = LintSeverity.WARNING, message = "Warning at line 10")
            )
        )

        // When: Running lint with modified ranges that don't include this file
        val results = lintExecutor.runLint(
            filePaths = listOf(filePath),
            projectPath = projectPath,
            modifiedCodeRanges = modifiedCodeRanges
        )

        // Then: No issues should be returned
        assertEquals(0, results.size, "Should return no results for file with no modified ranges")
    }

    @Test
    fun testLintResultFormatter_formatsCorrectly() {
        // Given: Lint results with various issues
        val lintResults = listOf(
            LintFileResult(
                filePath = "/project/src/Example.kt",
                linterName = "TestLinter",
                errorCount = 2,
                warningCount = 1,
                infoCount = 1,
                issues = listOf<LintIssue>(
                    LintIssue(
                        line = 10,
                        column = 5,
                        severity = LintSeverity.ERROR,
                        message = "Test error message",
                        rule = "test-rule-1"
                    ),
                    LintIssue(
                        line = 15,
                        column = 8,
                        severity = LintSeverity.WARNING,
                        message = "Test warning message",
                        rule = "test-rule-2"
                    )
                )
            )
        )

        // When: Formatting the results
        val formatted = lintResultFormatter.formatLintResults(lintResults)

        // Then: Should return formatted map with file path as key
        assertEquals(1, formatted.size)
        assertTrue(formatted.containsKey("/project/src/Example.kt"))

        val formattedText = formatted["/project/src/Example.kt"]!!
        assertTrue(formattedText.contains("File: /project/src/Example.kt"))
        assertTrue(formattedText.contains("Total Issues: 4"))
        assertTrue(formattedText.contains("Errors: 2"))
        assertTrue(formattedText.contains("Warnings: 1"))
        assertTrue(formattedText.contains("Info: 1"))
        assertTrue(formattedText.contains("[ERROR] Line 10: Test error message"))
        assertTrue(formattedText.contains("Rule: test-rule-1"))
    }

    @Test
    fun testLintResultFormatter_createsSummary() {
        // Given: Multiple file results
        val lintResults = listOf(
            LintFileResult(
                filePath = "/project/src/File1.kt",
                linterName = "TestLinter",
                errorCount = 2,
                warningCount = 1,
                infoCount = 0,
                issues = emptyList<LintIssue>()
            ),
            LintFileResult(
                filePath = "/project/src/File2.kt",
                linterName = "TestLinter",
                errorCount = 1,
                warningCount = 3,
                infoCount = 2,
                issues = emptyList<LintIssue>()
            )
        )

        // When: Creating summary
        val summary = lintResultFormatter.formatSummary(lintResults)

        // Then: Should summarize all results
        assertTrue(summary.contains("Files analyzed: 2"))
        assertTrue(summary.contains("Total errors: 3"))
        assertTrue(summary.contains("Total warnings: 4"))
        assertTrue(summary.contains("Total info: 2"))
    }

    @Test
    fun testDiffModels_areConstructedCorrectly() {
        // Test that DiffHunk and DiffLine are constructed with correct parameters
        val diffLine1 = DiffLine(
            type = DiffLineType.ADDED,
            content = "+    val newVariable = 42",
            oldLineNumber = null,
            newLineNumber = 15
        )

        val diffLine2 = DiffLine(
            type = DiffLineType.DELETED,
            content = "-    val oldVariable = 10",
            oldLineNumber = 14,
            newLineNumber = null
        )

        val diffLine3 = DiffLine(
            type = DiffLineType.CONTEXT,
            content = "     val existing = 20",
            oldLineNumber = 15,
            newLineNumber = 16
        )

        val diffHunk = DiffHunk(
            oldStartLine = 14,
            oldLineCount = 5,
            newStartLine = 15,
            newLineCount = 6,
            lines = listOf(diffLine1, diffLine2, diffLine3),
            header = "@@ -14,5 +15,6 @@"
        )

        // Verify construction
        assertEquals(DiffLineType.ADDED, diffLine1.type)
        assertEquals(15, diffLine1.newLineNumber)
        assertEquals(null, diffLine1.oldLineNumber)

        assertEquals(DiffLineType.DELETED, diffLine2.type)
        assertEquals(14, diffLine2.oldLineNumber)
        assertEquals(null, diffLine2.newLineNumber)

        assertEquals(3, diffHunk.lines.size)
        assertEquals(14, diffHunk.oldStartLine)
        assertEquals(15, diffHunk.newStartLine)
        assertEquals("@@ -14,5 +15,6 @@", diffHunk.header)
    }

    @Test
    fun testModifiedCodeRange_isDataClass() {
        // Test that ModifiedCodeRange is a data class and works correctly
        val range1 = ModifiedCodeRange(
            filePath = "/project/src/Example.kt",
            elementName = "myFunction",
            elementType = "FUNCTION",
            startLine = 10,
            endLine = 20,
            modifiedLines = listOf(12, 15)
        )

        val range2 = ModifiedCodeRange(
            filePath = "/project/src/Example.kt",
            elementName = "myFunction",
            elementType = "FUNCTION",
            startLine = 10,
            endLine = 20,
            modifiedLines = listOf(12, 15)
        )

        val range3 = ModifiedCodeRange(
            filePath = "/project/src/Example.kt",
            elementName = "otherFunction",
            elementType = "FUNCTION",
            startLine = 30,
            endLine = 40,
            modifiedLines = listOf(35)
        )

        // Data class equality
        assertEquals(range1, range2, "Identical ModifiedCodeRange should be equal")
        assertTrue(range1 != range3, "Different ModifiedCodeRange should not be equal")

        // Data class copy
        val range4 = range1.copy(elementName = "renamedFunction")
        assertEquals("renamedFunction", range4.elementName)
        assertEquals(range1.startLine, range4.startLine)
    }
}

/**
 * Test implementation of Linter for testing purposes
 */
class TestLinter : Linter {
    override val name: String = "TestLinter"
    override val description: String = "A test linter for integration tests"
    override val supportedExtensions: List<String> = listOf("kt", "kts", "java")

    private var testIssues: Map<String, List<LintIssue>> = emptyMap()
    private var available: Boolean = true

    /**
     * Set test issues for a specific file
     */
    fun setTestIssues(filePath: String, issues: List<LintIssue>) {
        testIssues = mapOf(filePath to issues)
    }

    /**
     * Set linter availability
     */
    fun setAvailable(available: Boolean) {
        this.available = available
    }

    override suspend fun isAvailable(): Boolean = available

    override suspend fun lintFile(filePath: String, projectPath: String): LintResult {
        val issues = testIssues[filePath] ?: emptyList()
        return LintResult(
            filePath = filePath,
            issues = issues,
            success = true,
            errorMessage = null,
            linterName = name
        )
    }

    override fun getInstallationInstructions(): String {
        return "TestLinter is a test implementation. No installation required."
    }
}
